#include "util.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <vector>

using Catch::Approx;
using namespace hms;

TEST_CASE( "Two floats are compared" )
{
	REQUIRE( hms::approxEqual(100*1.1, 110) == true );
	REQUIRE( hms::approxEqual(-1.0/10 - 1.0/10, -0.2) == true);
}

TEST_CASE( "Comparing two matrices",
	"[hms::operator==( const MatrixNNs&, const MatrixNNs& )]"
){		
	REQUIRE( approxEqual(
		Vector2s {1, 1}, 
		Vector2s {1 + (hms::epsilon / 2), 1 - (hms::epsilon / 2) }
	) );
	
	MatrixXXs m1 (3, 3);
	m1 <<
		0.1, 0.2, 0.1,
		0.2, 0.3, 0.2,
		1.2, 1.3, 1.2;
	
	MatrixXXs m2 (3, 3);
	m2 = 10 * m1;
	m2 /= 10;
	
	REQUIRE( m1 == m2 );
	REQUIRE( m1.col(0) == m1.col(2) );
}


TEST_CASE( "Interpolating between numbers and between vectors" )
{
	SECTION("scalar")
	{
		REQUIRE( interpolate(0., 10., 0.4) == Approx(4) );
	}
	
	SECTION("vector")
	{
		Vector2s a { 0, 4 };
		Vector2s b { 10, 14 };
		
		REQUIRE( interpolate(a, b, 0.6) == Vector2s { 6, 10 } );
	}
	
	SECTION("matrix column")
	{
		MatrixNNs<3,2> a;
		a <<
			0,  10,
			4,  14,
			10, 30;
		
		REQUIRE(
			interpolate(a.col(0), a.col(1), 0.3) == VectorNs<3> { 3, 7, 16 }
		);
	}
}


TEST_CASE( "Comparing arrays with out of order entries" )
{
	SECTION("std::vector"){
		REQUIRE( sameEntries( IdxVector{0,1,2,3}, IdxVector{1,3,0,2} ) == true );
		REQUIRE( sameEntries( IdxVector{0,1,2,3}, IdxVector{0,1,2,3} ) == true );
		REQUIRE( sameEntries( IdxVector{0,1,2,3}, IdxVector{0,1,2  } ) == false);
		REQUIRE( sameEntries( IdxVector{0,1,2,3}, IdxVector{0,1,3,3} ) == false);
	}
	SECTION("Eigen::Vector"){
		ArrayXi a (4); a << 0,1,2,3;
		ArrayXi b (4); b = a;
		REQUIRE( sameEntries( a, b ) == true );
		b << 1,3,0,2;
		REQUIRE( sameEntries( a, b ) == true );
		b << 0,1,3,3;
		REQUIRE( sameEntries( a, b ) == false);
		b.resize(3); b << 0,1,2;
		REQUIRE( sameEntries( a, b ) == false);
	}
		
}

TEST_CASE( "Checking if entry exists in index vector" )
{
	/* function has four flavours:
	 * hasEntry ( const Ref<ArrayXi>&, Index )
	 * hasEntry ( const IdxVector&, Index )
	 * hasEntry ( const std::vector<IdxVector>&, Index )
	 * hasEntry ( const std::vector<IdxVector>&, const IdxVector& )
	*/
	SECTION("Index in Eigen::Vector"){
		ArrayXi v (4); v << 0,1,2,3;
		REQUIRE( hasEntry( v, 0 ) == true );
		REQUIRE( hasEntry( v, 1 ) == true );
		REQUIRE( hasEntry( v, 2 ) == true );
		REQUIRE( hasEntry( v, 3 ) == true );
		REQUIRE( hasEntry( v, 4 ) == false);
	}
	SECTION("Index in std::vector"){
		REQUIRE( hasEntry( std::vector{0,1,2,3}, 0 ) == true );
		REQUIRE( hasEntry( std::vector{0,1,2,3}, 1 ) == true );
		REQUIRE( hasEntry( std::vector{0,1,2,3}, 2 ) == true );
		REQUIRE( hasEntry( std::vector{0,1,2,3}, 3 ) == true );
		REQUIRE( hasEntry( std::vector{0,1,2,3}, 4 ) == false);
	}
	SECTION("Index in std::vector of std::vectors"){
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 1 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 2 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 3 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 4 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 5 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 6 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 7 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 8 ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, 9 ) == false);
	}
	SECTION("std::vector in std::vector of std::vectors"){
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, {0,1,2,3} ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, {3,4,5,6} ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, {5,6,7,8} ) == true );
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, {0,1,2,4} ) == false);
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, {0,1,2  } ) == false);
		REQUIRE( hasEntry( std::vector<IdxVector>
			{ {0,1,2,3}, {3,4,5,6}, {5,6,7,8} }, {0,1,2,3,4} ) == false );
	}
}

TEST_CASE( "Copying std::vector to Eigen::Matrix" )
{
	SECTION( "std::vector of arithmetic types" ){
		std::vector<scalar> vec1 { 2, 3, 5, 1, 2 };
		ArrayXs a (5);
		a << 2, 3, 5, 1, 2;
		REQUIRE( (toEigen<ArrayXs>(vec1) == a).all() );

		std::vector<int> vec2 { 2, 3, 5, 1, 2 };
		VectorX<int> v (5);
		v << 2, 3, 5, 1, 2;
		REQUIRE( toEigen<VectorX<int>>(vec2) == v );
	}
	SECTION( "std::vector of Eigen column vectors" ){
		std::vector<Array3i> vec { {1,2,3}, {0,2,4}, {9,5,2} };
		Array3Xi a (3,3);
		a <<
			1, 0, 9,
			2, 2, 5,
			3, 4, 2;
		REQUIRE( (a == toEigen<Array3Xi>(vec) ).all() );
	}
}

TEST_CASE( "Copying Eigen type to std::vector" ){
	SECTION( "1D" ){
		VectorXi v (5);
		v << 2, 3, 4, 1, 5;
		REQUIRE( toVector(v) == std::vector<Index>{2, 3, 4, 1, 5} );
		ArrayXs a (3);
		a << 0.1, 2.2, -4.3;
		REQUIRE( toVector(a) == std::vector<scalar>{0.1, 2.2, -4.3} );
	}
	SECTION( "2D" ){
		Array2Xi m (2,2);
		m <<
			1, 4,
			3, 0;
		REQUIRE( toVector(m) == std::vector<Index>{1, 3, 4, 0} );
	}
}

TEST_CASE( "Finding key in key-value-pair" ){
	std::vector<std::pair<std::string, int>> dict
		{ {"c", 0}, {"d", 4}, {"a", 2}, {"b", 1} };
	sortByKeys(dict);
	SECTION( "Sorting key-value-pairs" ){
		auto req = [&](int i, std::string&& key, int val ){
			REQUIRE( dict[i] == std::pair<std::string, int>{key, val} );
		};
		req(0, "a", 2);
		req(1, "b", 1);
		req(2, "c", 0);
		req(3, "d", 4);
	}
	SECTION( "Getting iterator to key-value-pair" ){
		auto req = [&]( auto iter, std::string&& key, int val ){
			REQUIRE( *iter == std::pair<std::string, int>{key, val} );
		};
		req( findKey(dict, {"a"}), "a", 2);
		req( findKey(dict, {"b"}), "b", 1);
		req( findKey(dict, {"c"}), "c", 0);
		req( findKey(dict, {"d"}), "d", 4);
	}
	SECTION( "Getting index of key-value-pair in container" ){
		auto req = [&](int i, std::string&& key, int val ){
			REQUIRE( dict[i] == std::pair<std::string, int>{key, val} );
		};
		req( getKeyIndex(dict, {"a"}), "a", 2 );
		req( getKeyIndex(dict, {"b"}), "b", 1 );
		req( getKeyIndex(dict, {"c"}), "c", 0 );
		req( getKeyIndex(dict, {"d"}), "d", 4 );
		REQUIRE_THROWS( getKeyIndex(dict, {"ab"} ) );
	}
}